<?php

declare(strict_types=1);

namespace App\Controller\Publics\V2\Refund;

use App\Constants\Order\OrderCode;
use App\Constants\Payment\PaymentCode;
use App\Controller\Publics\V2\Controllers;
use App\Exception\InvalidRequestException;
use App\Factory\Refund\Factory;
use App\Factory\Wechat\Factory as WechatFactory;
use App\Services\Payment\Payment;
use EasyWeChat\Pay\Message;
use Hyperf\Collection\Arr;
use Hyperf\DbConnection\Db;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use function Hyperf\Config\config;

class Notify extends Controllers
{
    public function __construct(
        Payment $paymentServices
    )
    {
        parent::__construct();
        $this->services = $paymentServices;
    }

    /**
     * 退款回调
     * @param ResponseInterface $response
     * @param Factory $factory
     * @return Psr7ResponseInterface
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \Throwable
     */
    public function sandpayMini(ResponseInterface $response, Factory $factory):Psr7ResponseInterface
    {
        $input = $this->input();
        $config = config('cloud.payment.sandpayMini');
        $config = array_column($config, null, 'access_mid')[$input['accessMid']];

        $bizData = $factory->sandpayMiniNotify($input, $config);
        if (Arr::get($bizData, 'orderStatus') !== 'success') {
            return internalError();
        }
        $model = $this->services->where('no', $bizData['outOrderNo'])->firstOrFail();
        if ($model->process_at) {
            return internalError();
        }

        Db::transaction(function () use ($model, $bizData) {
            $model->fill([
                'process_at' => date('Y-m-d H:i:s'),
                'notify_body' => $bizData
            ])->save();
            $model->order->fill([
                'refund_at' => date('Y-m-d H:i:s'),
                'status' => OrderCode::STATUS_CANCEL,
                'pay_status' => OrderCode::PAY_STATUS_REFUND
            ])->save();
        });

        return $response->raw('respCode=000000');
    }

    /**
     * 微信小程序退款回调
     * @param int $id
     * @param WechatFactory $factory
     * @return Psr7ResponseInterface
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     * @throws \ReflectionException
     * @throws \Throwable
     */
    public function wechatMini(int $id, WechatFactory $factory): Psr7ResponseInterface
    {
        $config = config("cloud.payment.wechatMini.{$id}");
        $app = $factory->pay($config);

        $server = $app->getServer();
        $server->handleRefunded(function (Message $message, \Closure $next) use ($app, $config) {

            $data = $message->toArray();

            logs($data);

            $outRefundNo = $message->out_refund_no;

            $app->getValidator()->validate($app->getRequest());

            // 查询订单
            $response = $app->getClient()
                ->withSerialHeader(array_key_first($config['platform_certs']))
                ->get("v3/refund/domestic/refunds/{$outRefundNo}");
            $orderInfo = $response->toArray();
            /** @var \App\Model\Payment\Payment $paymentModel */
            $paymentModel = $this->services
                ->where('type', PaymentCode::TYPE_REFUND)
                ->where('no', $outRefundNo)
                ->firstOrFail();

            if ($paymentModel->process_at) {
                return $next($message);
            }

            $validatePrice = bcdiv((string)intval($orderInfo['amount']['refund']), '100', 2); // 验证金额
            if (bccomp($paymentModel->amount, $validatePrice, 2) !== 0) {
                logs("wechat price validate fail[{$paymentModel->amount} {$validatePrice}]");
                throw new InvalidRequestException();
            }

            Db::transaction(function () use ($paymentModel, $data) {
                $paymentModel->fill([
                    'process_at' => date('Y-m-d H:i:s'),
                    'notify_body' => $data
                ])->save();
                $paymentModel->order->fill([
                    'refund_at' => date('Y-m-d H:i:s'),
                    'status' => OrderCode::STATUS_CANCEL,
                    'pay_status' => OrderCode::PAY_STATUS_REFUND
                ])->save();
            });

            return $next($message);
        });

        return $server->serve();
    }

    /**
     * 统统付退款回调
     * @param int $id
     * @param Factory $factory
     * @param ResponseInterface $response
     * @return Psr7ResponseInterface
     */
    public function sumpayMini(int $id, Factory $factory, ResponseInterface $response): Psr7ResponseInterface
    {
        $config = config("cloud.payment.sumpayMini.{$id}");
        $input = $this->input();

        try {
            $data = $factory->sumpayMiniNotify($input, $config);

            if (Arr::get($data, 'status') != 1) {
                return $response->json([
                    'resp_code' => '000001',
                    'resp_msg' => '退款未成功'
                ]);
            }

            $refundNo = $data['refund_no'];
            $paymentModel = $this->services
                ->where('type', PaymentCode::TYPE_REFUND)
                ->where('no', $refundNo)
                ->firstOrFail();

            if ($paymentModel->process_at) {
                return $response->json([
                    'resp_code' => '000000',
                    'resp_msg' => ''
                ]);
            }

            Db::transaction(function () use ($paymentModel, $data) {
                $paymentModel->fill([
                    'process_at' => date('Y-m-d H:i:s'),
                    'notify_body' => $data
                ])->save();
                $paymentModel->order->fill([
                    'refund_at' => date('Y-m-d H:i:s'),
                    'status' => OrderCode::STATUS_CANCEL,
                    'pay_status' => OrderCode::PAY_STATUS_REFUND
                ])->save();
            });
        } catch (\Throwable $throwable) {
            logs($throwable);
            return $response->json([
                'resp_code' => '000002',
                'resp_msg' => $throwable->getMessage()
            ]);
        }

        return $response->json([
            'resp_code' => '000000',
            'resp_msg' => ''
        ]);
    }

    /**
     * 汇付退款回调
     * @param int $id
     * @param ResponseInterface $response
     * @param Factory $factory
     * @return Psr7ResponseInterface
     */
    public function chinapnrpayMini(int $id, ResponseInterface $response, Factory $factory): Psr7ResponseInterface
    {
        $config = config("cloud.payment.chinapnrpayMini.{$id}");
        $input = $this->input();

        try {
            $data = $factory->chinapnrpayMiniNotify($input, $config);
            if ((Arr::has($data, 'errCode') && Arr::get($data, 'errCode') !== '000000') ||
                Arr::get($data, 'refundResult') !== '10') {
                return $response->raw('FAILED');
            }

            $refundNo = $data['refundOrderId'];
            $paymentModel = $this->services
                ->where('type', PaymentCode::TYPE_REFUND)
                ->where('no', $refundNo)
                ->firstOrFail();

            if ($paymentModel->process_at) {
                return $response->raw('SUCCESS');
            }

            Db::transaction(function () use ($paymentModel, $data) {
                $paymentModel->fill([
                    'process_at' => date('Y-m-d H:i:s'),
                    'notify_body' => $data
                ])->save();
                $paymentModel->order->fill([
                    'refund_at' => date('Y-m-d H:i:s'),
                    'status' => OrderCode::STATUS_CANCEL,
                    'pay_status' => OrderCode::PAY_STATUS_REFUND
                ])->save();
            });

        } catch (\Throwable $throwable) {
            logs($throwable);
            return $response->raw('FAILED');
        }

        return $response->raw('SUCCESS');
    }
}
